package ai.connectfour.algorithm;

import java.util.Observable;

import ai.connectfour.Config;
import ai.connectfour.entities.Action;
import ai.connectfour.entities.Player;
import ai.connectfour.entities.State;


public class IterativeDeepening extends Observable{
	
	private State currentGameState;
	private State stateInAlgo;
	private int rekursionDepth;
	private int maxRekursionDepth;
	private long startTime;
	
	public void setCurrentGameState(State gameState) {
		this.currentGameState = gameState;
		setChanged();
		notifyObservers(this.currentGameState);
	}

	public State getCurrentGameState() {
		return currentGameState;
	}

	public IterativeDeepening(){
		currentGameState = new State();
	}
		
	public Action makeDecision() throws Exception{
		startTime = System.currentTimeMillis();
		maxRekursionDepth=1;
		Action decisionAction = null;
		Action tmpAction = null;
		while(elapsedTime() < Config.TIME_LIMIT){
			tmpAction = limitedDepthSearch();
			if(elapsedTime() <= Config.TIME_LIMIT || decisionAction == null){
				decisionAction = tmpAction;
			}
			maxRekursionDepth++;
		}
		return decisionAction;
	}
	
	public long elapsedTime(){
		return System.currentTimeMillis() - startTime;
	}
	
	private Action limitedDepthSearch() throws Exception{
		stateInAlgo = new State(currentGameState);
		rekursionDepth=0;
		int bestUtility = Integer.MIN_VALUE;
		int utility;
		int alpha = Integer.MIN_VALUE;
		int beta = Integer.MAX_VALUE;
		Action bestAction = null;
		for(int actionNumber=0; actionNumber<Config.DIMENSION_COLS;actionNumber++){
			Action action = stateInAlgo.getPossibleActionNumber(actionNumber);
			if(action != null){
				action.setPlayer(Player.MAX);
				stateInAlgo.doAction(action);
				rekursionDepth++;
				utility = minValue(alpha, beta);
				rekursionDepth--;
				if(utility > bestUtility){
					bestUtility = utility;
					bestAction = action;
				}
				if(bestUtility > beta){
					stateInAlgo.undoAction(action);
					bestAction.setPlayer(Player.MAX);
					return bestAction;
				}
				alpha = bestUtility > alpha ? bestUtility : alpha;
				stateInAlgo.undoAction(action);
			}
		}
		bestAction.setPlayer(Player.MAX);
		return bestAction;
	}
	
	private int minValue(double alpha, double beta) throws Exception{
		if(stateInAlgo.terminalTest()){
			return stateInAlgo.getTerminalUtility(rekursionDepth);
		}
		if(rekursionDepth >= maxRekursionDepth || elapsedTime() > Config.TIME_LIMIT){
			return stateInAlgo.getUtility(rekursionDepth);
		}
				
		int bestUtility = Integer.MAX_VALUE;
		int utility;
		for(int actionNumber=0; actionNumber<Config.DIMENSION_COLS;actionNumber++){
			Action action = stateInAlgo.getPossibleActionNumber(actionNumber);
			if(action != null){
				action.setPlayer(Player.MIN);
				stateInAlgo.doAction(action);
				rekursionDepth++;
				utility = maxValue(alpha, beta);
				rekursionDepth--;
				
				if(utility < bestUtility){
					bestUtility = utility;
					if(bestUtility <= alpha){
						stateInAlgo.undoAction(action);
						return bestUtility;
					}
					beta = bestUtility < beta? bestUtility : beta;
				}
				stateInAlgo.undoAction(action);
			}
		}
		return bestUtility;
	}
	
	private int maxValue(double alpha, double beta) throws Exception{
		if(stateInAlgo.terminalTest()){
			return stateInAlgo.getTerminalUtility(rekursionDepth);
		}
		if(rekursionDepth >= maxRekursionDepth || elapsedTime() > Config.TIME_LIMIT){
			return stateInAlgo.getUtility(rekursionDepth);
		}
				
		int bestUtility = Integer.MIN_VALUE;
		int utility;

		for(int actionNumber=0; actionNumber<Config.DIMENSION_COLS;actionNumber++){
			Action action = stateInAlgo.getPossibleActionNumber(actionNumber);
			if(action != null){
				action.setPlayer(Player.MAX);
				stateInAlgo.doAction(action);
				rekursionDepth++;
				utility = minValue(alpha, beta);
				rekursionDepth--;
				
				if(utility > bestUtility){
					bestUtility = utility;
					if(bestUtility >= beta){
						stateInAlgo.undoAction(action);
						return bestUtility;
					}
					alpha = bestUtility > alpha ? bestUtility : alpha;
				}
				stateInAlgo.undoAction(action);
			}
		}
		return bestUtility;
	}	
	
	public void doTurn(Action action) {
		this.currentGameState.doAction(action);
		this.setChanged();
		this.notifyObservers(this.currentGameState);
	}

	public void undoTurn(Action action) {
		this.currentGameState.undoAction(action);
		this.setChanged();
		this.notifyObservers(this.currentGameState);		
	}
}
